package io.renren.wechat.service.suite;

import io.renren.wechat.config.WechatProperties;
import io.renren.wechat.service.suite.dto.SuiteAccessTokenResponse;
import io.renren.wechat.support.ResponseEntityUtils;
import io.renren.wechat.support.WechatConstants;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 获取第三方应用凭证
 * <p>
 * 由于第三方服务商可能托管了大量的企业，其安全问题造成的影响会更加严重，故API中除了合法来源IP校验之外，还额外增加了suite_ticket作为安全凭证。
 * 获取suite_access_token时，需要suite_ticket参数。suite_ticket由企业微信后台定时推送给“指令回调URL”，每十分钟更新一次，见推送suite_ticket。
 * suite_ticket实际有效期为30分钟，可以容错连续两次获取suite_ticket失败的情况，但是请永远使用最新接收到的suite_ticket。
 * 通过本接口获取的suite_access_token有效期为2小时，开发者需要进行缓存，不可频繁获取。
 *
 * @author dmcp@qq.com
 * @since 2021-11-15 11:31
 */
@Component
public class SuiteAccessTokenService {
    private static final Logger LOGGER = LoggerFactory.getLogger(SuiteAccessTokenService.class);
    private static final int SUITE_ACCESS_TOKEN_TIMEOUT_IN_SECONDS = 7200;
    private RestTemplate fastRestTemplate;
    private StringRedisTemplate stringRedisTemplate;
    private WechatProperties wechatProperties;
    private SuiteTicketService suiteTicketService;


    /**
     * 获取第三方应用凭证
     *
     * @return suite_access_token
     */
    public String getSuiteAccessToken() {
        final String suiteId = wechatProperties.getSuiteId();
        final String suiteSecret = wechatProperties.getSuiteSecret();
        final String suiteTicket = suiteTicketService.getSuiteTicket(suiteId);
        LOGGER.info("getSuiteAccessToken, suiteId = {}, suiteSecret = {}, suiteTicket = {}", suiteId, suiteSecret, suiteTicket);

        return this.getSuiteAccessToken(suiteId, suiteSecret, suiteTicket);
    }

    /**
     * 第二个应用，获取第三方应用凭证
     *
     * @return suite_access_token
     */
    public String getSuiteAccessToken2() {
        final String suiteId = wechatProperties.getSuiteId2();
        final String suiteSecret = wechatProperties.getSuiteSecret2();
        final String suiteTicket = suiteTicketService.getSuiteTicket(suiteId);
        LOGGER.info("getSuiteAccessToken2, suiteId = {}, suiteSecret = {}, suiteTicket = {}", suiteId, suiteSecret, suiteTicket);

        return this.getSuiteAccessToken(suiteId, suiteSecret, suiteTicket);
    }

    /**
     * 获取第三方应用凭证
     *
     * @param suiteId     以ww或wx开头应用id（对应于旧的以tj开头的套件id）
     * @param suiteSecret 应用secret
     * @param suiteTicket 企业微信后台推送的ticket
     * @return suite_access_token
     */
    public String getSuiteAccessToken(String suiteId, String suiteSecret, String suiteTicket) {
        String suiteAccessTokenFromCache = getSuiteAccessTokenFromCache(suiteId);
        if (StringUtils.isBlank(suiteAccessTokenFromCache)) {
            LOGGER.warn("suite access token in cache with suiteId: {}", suiteId);
        }

        if (StringUtils.isNotBlank(suiteAccessTokenFromCache)) {
            return suiteAccessTokenFromCache;
        }

        return doGetSuiteAccessToken(suiteId, suiteSecret, suiteTicket);
    }

    private String doGetSuiteAccessToken(String suiteId, String suiteSecret, String suiteTicket) {
        final SuiteAccessTokenResponse accessToken = doObtainSuiteAccessToken(suiteId, suiteSecret, suiteTicket);

        LOGGER.info("doGetSuiteAccessToken, suiteId = {}, accessToken = {}", suiteId, accessToken);
        cacheSuiteAccessToken(accessToken, suiteId);

        return accessToken.getSuiteAccessToken();
    }

    protected SuiteAccessTokenResponse doObtainSuiteAccessToken(String suiteId, String suiteSecret, String suiteTicket) {
        final String getSuiteAccessTokenUrl = WechatConstants.SUITE_ACCESS_TOKEN_URL;
        LOGGER.debug("getting suite access token with url: [{}]", getSuiteAccessTokenUrl);

        Map<String, String> requestParams = new HashMap<>();
        requestParams.put("suite_id", suiteId);
        requestParams.put("suite_secret", suiteSecret);
        requestParams.put("suite_ticket", suiteTicket);

        final ResponseEntity<SuiteAccessTokenResponse> responseEntity = fastRestTemplate.postForEntity(getSuiteAccessTokenUrl,
                requestParams, SuiteAccessTokenResponse.class);
        LOGGER.debug("get suite access token response entity: {}", responseEntity);

        if (!responseEntity.getStatusCode().is2xxSuccessful()) {
            throw new RuntimeException("调用获取suite accessToken请求执行失败");
        }

        final SuiteAccessTokenResponse tokenResponse = responseEntity.getBody();
        if (tokenResponse == null) {
            LOGGER.warn("obtain suite access token failed, response body is empty!");
            throw new RuntimeException("获取suite accessToken失败，响应为空");
        }

        boolean isError = ResponseEntityUtils.isError(responseEntity) || tokenResponse.isError();
        if (isError) {
            LOGGER.warn(">>>>>>>obtain suite access token failed, errMsg: {}", tokenResponse.getErrMsg());
            throw new RuntimeException("获取suite accessToken失败");
        }

        LOGGER.info("get_new_suite_access_token successfully: [{}]", tokenResponse.getSuiteAccessToken());
        return tokenResponse;
    }

    public void cacheSuiteAccessToken(SuiteAccessTokenResponse accessToken, String suiteId) {
        final String cacheKey = buildSuiteAccessTokenCacheKey(suiteId);

        stringRedisTemplate.opsForValue().set(cacheKey, accessToken.getSuiteAccessToken(),
                accessToken.getExpiresIn(), TimeUnit.SECONDS);
    }

    public String getSuiteAccessTokenFromCache(String suiteId) {
        final String cacheKey = buildSuiteAccessTokenCacheKey(suiteId);

        return stringRedisTemplate.opsForValue().get(cacheKey);
    }

    private String buildSuiteAccessTokenCacheKey(String suiteId) {
        return String.format("suite_access_token:%s", suiteId);
    }

    @Autowired
    public void setSuiteTicketService(SuiteTicketService suiteTicketService) {
        this.suiteTicketService = suiteTicketService;
    }

    @Autowired
    public void setWechatProperties(WechatProperties wechatProperties) {
        this.wechatProperties = wechatProperties;
    }

    @Autowired
    public void setFastRestTemplate(RestTemplate fastRestTemplate) {
        this.fastRestTemplate = fastRestTemplate;
    }

    @Autowired
    public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
}
